﻿using Newtonsoft.Json.Linq;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

using Utils;

namespace MicroDb.Helper
{
    static class DBReaderHelper
    {
        #region 私有方法


        public static List<T> ToList<T>(System.Data.Common.DbDataReader reader)
        {
            var type = typeof(T);
            var array = new List<T>();
            var constructors = type.GetConstructors();
            if (constructors.Length == 1 && constructors[0].GetParameters().Length > 0)
            {
                var anylist = LoadAnonymous(type).ToArray();
                while (reader.Read())
                {
                    array.Add((T)ReaderAnonymous(reader, type, anylist));
                }
            }
            else if (type.IsValueType)
            {
                type = Nullable.GetUnderlyingType(type) ?? type;
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add((T)value);
                }
            }
            else if (type.IsConvertible())
            {
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add((T)value);
                }
            }
            else if (type.IsAnonymousType())
            {
                var anylist = LoadAnonymous(type).ToArray();
                while (reader.Read())
                {
                    array.Add((T)ReaderAnonymous(reader, type, anylist));
                }
            }
            else if (typeof(JToken).IsAssignableFrom(type))
            {
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add((T)value);
                }
            }
            else
            {
                IDictionary<string, Action<object, object>> dic = GetMap(type);
                while (reader.Read())
                {
                    array.Add((T)ReaderEntity(reader, type, dic));
                }
            }
            return array;
        }

        public static ArrayList ToArrayList(System.Data.Common.DbDataReader reader, Type type)
        {
            ArrayList array = new ArrayList();
            if (!reader.HasRows)
                return array;
            var constructors = type.GetConstructors();

            if (constructors.Length == 1 && constructors[0].GetParameters().Length > 0)
            {
                var anylist = LoadAnonymous(type).ToArray();
                while (reader.Read())
                {
                    array.Add(ReaderAnonymous(reader, type, anylist));
                }
            }
            else if (type.IsValueType)
            {
                type = Nullable.GetUnderlyingType(type) ?? type;
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add(value);
                }
            }
            else if (type.IsConvertible())
            {
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add(value);
                }
            }
            else if (type.IsAnonymousType())
            {
                var anylist = LoadAnonymous(type).ToArray();
                while (reader.Read())
                {
                    array.Add(ReaderAnonymous(reader, type, anylist));
                }
            }
            else if (typeof(JToken).IsAssignableFrom(type))
            {
                while (reader.Read())
                {
                    object value = reader[0];
                    ConvertChangeType(ref value, type);
                    array.Add(value);
                }
            }
            else
            {
                IDictionary<string, Action<object, object>> dic = GetMap(type);
                while (reader.Read())
                {
                    array.Add(ReaderEntity(reader, type, dic));
                }
            }

            return array;
        }

        private static object ReaderEntity(System.Data.Common.DbDataReader dr, Type type, IDictionary<string, Action<object, object>> dic)
        {
            object model = Activator.CreateInstance(type);
            LoadEntity(dr, dic, ref model);
            return model;
        }

        private static object ReaderAnonymous(System.Data.Common.DbDataReader dr, Type type, Func<System.Data.Common.DbDataReader, object>[] funcs)
        {
            return Activator.CreateInstance(type, funcs.Select(fun => fun(dr)).ToArray());
        }

        static IDictionary<Type, object> dicCache = new Dictionary<Type, object>();
        private static readonly object lockobj = new object();
        static IDictionary<string, Action<object, object>> GetMap(Type type)
        {
            if (!dicCache.TryGetValue(type, out object dic))
            {
                lock (lockobj)
                {
                    return InitMap(type);
                }
            }
            return (IDictionary<string, Action<object, object>>)dic;
        }


        private static void LoadEntity<T>(System.Data.Common.DbDataReader dr, IDictionary<string, Action<T, object>> dic, ref T model)
        {
            for (int i = 0; i < dr.FieldCount; i++)
            {
                var fieldName = dr.GetName(i);
                if (dic.ContainsKey(fieldName))
                {
                    dic[fieldName](model, dr[fieldName]);
                }
            }
        }
        /// <summary>
        /// 初始化对象映射
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <returns></returns>
        private static IDictionary<string, Action<object, object>> InitMap(Type type)
        {
            var _dic = new Dictionary<string, Action<object, object>>(StringComparer.OrdinalIgnoreCase);
            PropertyInfo[] ps =
                type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
                                   BindingFlags.IgnoreCase);
            foreach (PropertyInfo pi in ps)
            {
                MethodInfo setMethodInfo = pi.GetSetMethod(true);
                if (setMethodInfo == null)
                    continue;
                Action<object, object> func = SetDelegate(setMethodInfo, pi.PropertyType, type);
                _dic.Add(ChangeName(pi.Name), func);
            }
            dicCache[type] = _dic;
            return _dic;
        }

        static Action<object, object> SetDelegate(MethodInfo m, Type type, Type TypeObj)
        {
            return (obj, value) =>
            {
                if (value is DBNull)
                {
                    return;
                }
                try
                {
                    ConvertChangeType(ref value, type);
                    m.Invoke(obj, new[] { value });
                }
                catch
                {

                }
            };
        }
        private static void ConvertChangeType(ref object value, Type type)
        {
            if (type.Equals(typeof(JObject)))
            {
                value = JObject.Parse(value.ToString());
                return;
            }
            if (type.Equals(typeof(JToken)))
            {
                value = JToken.Parse(value.ToString());
                return;
            }
            if (type.Equals(typeof(JArray)))
            {
                value = JArray.Parse(value.ToString());
                return;
            }
            if (type.IsValueType)
            {
                type = Nullable.GetUnderlyingType(type) ?? type;
            }
            value = Convert.ChangeType(value, type);
        }

        private static string ChangeName(string name)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var ch in name)
            {
                if (ValidatorHelper.Between(ch, 'A', 'Z'))
                {
                    if (sb.Length > 0)
                        sb.Append('_');
                    sb.Append((char)(ch + 0x20));
                }
                else
                {
                    sb.Append(ch);
                }
            }
            return sb.ToString();
        }
        private static IEnumerable<Func<System.Data.Common.DbDataReader, object>> LoadAnonymous(Type type)
        {
            var parameters = type.GetConstructors()[0].GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                int j = i;
                yield return (dr) =>
                {
                    object val = dr[j];
                    if (val != null && val != DBNull.Value)
                    {
                        ConvertChangeType(ref val, parameters[j].ParameterType);
                        return val;
                    }
                    else
                    {
                        return null;
                    }
                };
            }
        }


        #endregion
    }
}
